using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Diagnostics;
using Look_Through_Windows.Helper;

//using System.Net;

namespace Look_Through_Windows.Network
{
    public delegate void ConnectionDelegate( NetConnection conn );
    public delegate void ConnectionStateDelegate( NetConnection conn, int newstate );
    public delegate void MessageRecieveDelegate( NetMessage msg, NetConnection conn );

    public class NetConnection
    {
        private static int SleeptimeNormal = 10;
        private static int SleeptimeLower = 20;
        private static int SleeptimeLowest = 30;

        private Thread MessageRecieverThread;
        private Thread MessageSendThread;
        private int Sleeptime;
        private Queue<NetMessage> Messages = new Queue<NetMessage>( );
        private Queue<NetMessage> ToSend = new Queue<NetMessage>( );
        private static int MESSAGELIMIT = 1000000;

        //private int CurrIncMsgID = 0;
        //private int CurrOutMsgID = 0;

        public static NetConnection ConnectTo( string host, int port, ThreadPriority priority )
        {
            TcpClient client = new TcpClient( );
            client.Connect( host, port );

            return new NetConnection( client, priority );
        }

        private int _state = 0;

        public int State
        {
            get { return _state; }
            set
            {
                if ( _state != value && ConnectionStateChange != null )
                    ConnectionStateChange( this, value );
                _state = value;
            }
        }

        public readonly TcpClient Client;
        public readonly NetworkStream NetStream;
        public event ConnectionDelegate ConnectionDisconnect;
        public event ConnectionDelegate ConnectionClose;
        public event ConnectionStateDelegate ConnectionStateChange;
        public event MessageRecieveDelegate MessageRecieve;
        public object Tag;

        public bool Connected { get { return Client.Connected; } }

        private const int maxBufferSize = 512 * 1024;
        private byte[] buffer = new byte[ maxBufferSize ];
        private int curr_pos = 0;
        private int curr_length = 0;
        private List<byte> msgbuffer = new List<byte>( 32 );
        private bool running = true;
        private object sendlocker = new object( );

        public override string ToString( )
        {
            try
            {
                return ( Client.Client.RemoteEndPoint as IPEndPoint ).ToString( );
            }
            catch { return "Not Connected"; }
        }

        public bool IsLocal
        {
            get
            {
                if ( !Connected ) return false;
                return ( Client.Client.RemoteEndPoint as IPEndPoint ).Address.Equals( Localhost );
            }
        }

        static IPAddress Localhost = new IPAddress( new byte[] { 127, 0, 0, 1 } );

        public NetConnection( TcpClient client, ThreadPriority priority = ThreadPriority.Normal )
        {
            Client = client;
            NetStream = Client.GetStream( );
            Client.ReceiveBufferSize = 512 * 1024;
            Client.SendBufferSize = 512 * 1024;
            NetStream.BeginRead( buffer, 0, maxBufferSize, RecieveAsync, null );

            MessageRecieverThread = new Thread( MessageRecieveRoutine );
            SetPriority( priority );
            MessageRecieverThread.IsBackground = true;
            MessageRecieverThread.Name = "Message Receiver for " + IP;
            //MessageRecieverThread.Start( );

            MessageSendThread = new Thread( MessageSendRoutine );
            MessageSendThread.IsBackground = true;
            MessageSendThread.Name = "Message Sender for " + IP;
            MessageSendThread.Start( );
        }

        public void SetPriority( ThreadPriority priority )
        {
            switch ( priority )
            {
                case ThreadPriority.Normal:
                    Sleeptime = SleeptimeNormal;
                    break;
                case ThreadPriority.BelowNormal:
                    Sleeptime = SleeptimeLower;
                    break;
                case ThreadPriority.Lowest:
                default:
                    Sleeptime = SleeptimeLowest;
                    break;
            }
            MessageRecieverThread.Priority = priority;
        }

        public void CheckMessages( )
        {
            Queue<NetMessage> msgs = new Queue<NetMessage>( );

            lock ( Messages )
                while ( Messages.Count > 0 )
                    msgs.Enqueue( Messages.Dequeue( ) );

            foreach ( var msg in msgs )
                if ( MessageRecieve != null )
                    MessageRecieve( msg, this );
        }

        public bool CheckOneMessages( )
        {
            NetMessage msg = null;
            lock ( Messages )
                if ( Messages.Count > 0 )
                    msg = Messages.Dequeue( );
            if ( msg != null && MessageRecieve != null )
                MessageRecieve( msg, this );
            return msg != null;
        }

        private void MessageSendRoutine( )
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
            try
            {
                NetMessage msg = null;
                while ( true )
                {
                    bool standby = true;

                    lock ( ToSend )
                        if ( ToSend.Count > 0 )
                        {
                            msg = ToSend.Dequeue( );
                            //Console.WriteLine( "Dequeuing " + msg.Data.Count + " bytes" );
                        }
                    if ( msg != null )
                    {
                        //Console.WriteLine( "Sending " + msg.Data.Count + " byte @ " + this.ToString( ) + " (" + Thread.CurrentThread.Name + ")" );
                        Send( msg.Data );
                        standby = false;
                        msg = null;
                    }

                    if ( standby ) Thread.Sleep( 10 );
                    /*if ( standby )
                        Thread.Sleep( 10 );
                    else Thread.Sleep( Sleeptime / 10 );*/
                }
            }
            catch ( ThreadAbortException ) { throw; }
        }

        private void MessageRecieveRoutine( )
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            try
            {
                while ( true )
                {
                    bool standby = true;

                    //if ( NetStream.DataAvailable )
                    {
                        int amount = NetStream.Read( buffer, 0, maxBufferSize );
                        Console.WriteLine( amount + " bytes receiving" );

                        for ( int i = 0 ; i < amount ; i++ )
                        {
                            if ( curr_pos == 0 )
                                curr_length = buffer[ i ];
                            else if ( curr_pos == 1 )
                                curr_length += buffer[ i ] << 8;
                            else if ( curr_pos == 2 )
                            {
                                curr_length += buffer[ i ] << 16;
                                //Console.WriteLine( "next length: " + curr_length + " (" + Thread.CurrentThread.Name + ")" );
                            }
                            else
                            {
                                msgbuffer.Add( buffer[ i ] );

                                if ( curr_pos == curr_length + 3 - 1 )
                                {
                                    //Logger.netIn(msgbuffer.ToArray());
#if DEBUG
                                    //lock (typeof(NetConnection))
                                    //    File.AppendAllText("login.txt", "[" + DateTime.Now.ToString() + "] " + BitConverter.ToString(msgbuffer.ToArray()) + "\r\n");
#endif
                                    NetMessage msg = new NetMessage( msgbuffer );
                                    msg.Sender = this;
                                    msgbuffer = new List<byte>( 32 );
                                    curr_pos = -1;
                                    curr_length = 0;
                                    Console.WriteLine( "got msg  with " + msg.Data.Count + " bytes" );

                                    //if ( msg.Data.Count < 10000 )
                                    lock ( Messages )
                                    {
                                        if ( Messages.Count < MESSAGELIMIT )
                                            Messages.Enqueue( msg );
                                        else throw new Exception( "wow Oo" );
                                    }
                                }
                            }
                            curr_pos++;
                        }

                        standby = false;
                    }

                    if ( standby )
                        Thread.Sleep( Sleeptime );
                    else Thread.Sleep( Sleeptime / 10 );
                }
            }
            catch ( ThreadAbortException ) { throw; }

            /*return;
            try
            {
                NetMessage msg = null;
                while (true)
                {
                    bool standby = true;

                    lock (Messages)
                        if (Messages.Count > 0)
                            msg = Messages.Dequeue();
                    if (msg != null)
                    {
                        if (MessageRecieve != null)
                            MessageRecieve(msg, this);
                        standby = false;
                        msg = null;
                    }

                    if (standby)
                        Thread.Sleep(Sleeptime);
                    else Thread.Sleep(Sleeptime / 10);
                }
            }
            catch (ThreadAbortException) { throw; }*/
        }

        public void Close( )
        {
            if ( !running ) return;
            running = false;
            MessageRecieverThread.Abort( );
            lock ( Messages )
                Messages.Clear( );
            if ( ConnectionClose != null )
                ConnectionClose( this );
            Client.Client.Close( 3 );
            Client.Close( );
        }

        public void Disconnect( Exception ex )
        {
            if ( ConnectionDisconnect != null )
                ConnectionDisconnect( this );
            Close( );
        }

        public void Send( NetMessage msg )
        {
            if ( !Connected )
                return;
            lock ( ToSend )
            {
                //Console.WriteLine( "Enqueueing " + msg.Data.Count + " bytes" );
                ToSend.Enqueue( msg );
            }
        }

        private void Send( List<byte> bytes )
        {
            if ( !running ) return;
            byte[] data = CreateData( bytes );
#if DEBUG
            //lock (typeof(NetConnection))
            //    File.AppendAllText("logout.txt", "[" + DateTime.Now.ToString() + "] " + BitConverter.ToString(data) + "\r\n");
#endif
            //Logger.netOut(data);
            try
            {
                lock ( sendlocker )
                {
                    //NetStream.WriteTimeout = 3000;
                    NetStream.Write( data, 0, data.Length );
                }
            }
            catch ( IOException ex )
            {
                /*if ( !ex.Message.Contains( "aborted" )
                    && !ex.Message.Contains( "forcibly" ) )
                    throw;*/

                Debugger.Break( );

                Disconnect( ex );
            }
        }

        private byte[] CreateData( List<byte> bytes )
        {
            using ( MemoryStream ms = new MemoryStream( ) )
            {
                Stopwatch sw = new Stopwatch( );
                sw.Start( );
                using ( DeflateStream comp = new DeflateStream( ms, CompressionLevel.Optimal, false ) )
                {
                    comp.Write( bytes.ToArray( ), 0, bytes.Count );
                }
                var cbytes = ms.ToArray( );
                //Console.WriteLine( bytes.Count + " -> " + cbytes.Length + " compression: " + ( cbytes.Length / ( float )bytes.Count ).ToString( "0.0%" ) + ", " + sw.Elapsed.TotalSeconds.ToDurationString( ) );

                if ( cbytes.Length >= 256 * 256 * 256 ) throw new ArgumentOutOfRangeException( );
                byte[] packet = new byte[ cbytes.Length + 3 ];
                cbytes.CopyTo( packet, 3 );
                packet[ 0 ] = ( byte )( cbytes.Length % 256 );
                packet[ 1 ] = ( byte )( ( cbytes.Length >> 8 ) % 256 );
                packet[ 2 ] = ( byte )( ( cbytes.Length >> 16 ) % 256 );
                return packet;
            }
        }

        private void RecieveAsync( IAsyncResult res )
        {
            if ( !running ) return;
            try
            {
                int amount = NetStream.EndRead( res );
                //Console.WriteLine( amount + " bytes receiving" );

                for ( int i = 0 ; i < amount ; i++ )
                {
                    if ( curr_pos == 0 )
                        curr_length = buffer[ i ];
                    else if ( curr_pos == 1 )
                        curr_length += buffer[ i ] << 8;
                    else if ( curr_pos == 2 )
                    {
                        curr_length += buffer[ i ] << 16;
                        //Console.WriteLine( "next length: " + curr_length );
                    }
                    else
                    {
                        msgbuffer.Add( buffer[ i ] );

                        if ( curr_pos == curr_length + 3 - 1 )
                        {
                            //Logger.netIn(msgbuffer.ToArray());
#if DEBUG
                            //lock (typeof(NetConnection))
                            //    File.AppendAllText("login.txt", "[" + DateTime.Now.ToString() + "] " + BitConverter.ToString(msgbuffer.ToArray()) + "\r\n");
#endif

                            //decrompress
                            List<byte> decompmsgbuffer;

                            using ( MemoryStream ms = new MemoryStream( msgbuffer.ToArray( ) ) )
                            {
                                using ( MemoryStream mstarget = new MemoryStream( ) )
                                {
                                    using ( DeflateStream comp = new DeflateStream( ms, CompressionMode.Decompress, false ) )
                                    {
                                        comp.CopyTo( mstarget );
                                    }
                                    decompmsgbuffer = new List<byte>( mstarget.ToArray( ) );
                                }
                            }

                            NetMessage msg = new NetMessage( decompmsgbuffer );
                            msg.Sender = this;
                            msgbuffer = new List<byte>( 32 );
                            curr_pos = -1;
                            curr_length = 0;
                            //Console.WriteLine( "got msg  with " + msg.Data.Count + " bytes" );

                            lock ( Messages )
                            {
                                if ( Messages.Count < MESSAGELIMIT )
                                    Messages.Enqueue( msg );
                                else throw new Exception( "wow Oo" );
                            }
                        }
                    }
                    curr_pos++;
                }

                NetStream.BeginRead( buffer, 0, maxBufferSize, RecieveAsync, null );
            }
            catch ( IOException ex )
            {
                if ( ex.Message.Contains( "forc" )
                    || ex.Message.Contains( "aborted" ) )
                {
                    Disconnect( ex );
                    return;
                }

#if DEBUG
                Debugger.Break( );
                throw ex;
#else
                //Logger.error(2, "Net Connection (" + IP + ") Error", ex);
                Disconnect( ex );
#endif
            }
            catch ( ObjectDisposedException )
            {
                Close( );
            }
        }

        public string IP
        {
            get
            {
                return Client.Client.RemoteEndPoint.ToString( );
            }
        }
    }
}